/*
 * SiFiSy - A File System Simulator
 * Copyright (C) 2010  SevenSoft
 *
 * This file is part of SiFiSy.
 *
 * SiFiSy is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * SiFiSy is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with SiFiSy.  If not, see <http://www.gnu.org/licenses/>.
 */

package sifisy.core;

import java.util.Vector;
import org.jdom.*;

/**
 * Ext2 class describes the type of file system Ext2
 * @author Samuele Faggian
 * @version %I%, %G%
 * @createData 17/02/2010
 */
public class Ext2 extends Simulation {
    private int addressLevel;
    private int firstInodeDirectBlock;
    private int inodeDimension;
    private long inodeNumberMaxFile;
    private long usedInodeNumber = 0;
    
    /**
     * Constructor
     */
    public Ext2() {
        super();
    }

    /**
     * Save the simulation via XML file.
     * @param path
     */
    @Override
    void save(Element simulationRoot) {
        super.save(simulationRoot);
        simulationRoot.addContent("\n    ");
        Element FileSystem = new Element("fileSystem");
        FileSystem.addContent("\n        ");
        FileSystem.addContent(new Element("type").addContent("Ext2"));
        FileSystem.addContent("\n        ");
        FileSystem.addContent(new Element("inodeDimension").
                addContent(String.valueOf(inodeDimension)));
        FileSystem.addContent("\n        ");
        FileSystem.addContent(new Element("firstInodeDirectBlock").
                addContent(String.valueOf(firstInodeDirectBlock)));
        FileSystem.addContent("\n        ");
        FileSystem.addContent(new Element("addressLevel").
                addContent(String.valueOf(addressLevel)));
        FileSystem.addContent("\n        ");
        FileSystem.addContent(new Element("inodeDimension").
                addContent(String.valueOf(inodeDimension)));
        FileSystem.addContent("\n    ");
        simulationRoot.addContent(FileSystem);
        simulationRoot.addContent("\n");
    }

    /**
     * Method that calculates the number of Inode of a file
     * @param sizeFile - the size of file you want to know the number of Inode
     * @return an int with a number of Inode are used to store the file
     */
    long getInodeNumber(long sizeFile) {
        long countInode = 0;
        int fullLevel=0;
        boolean max = false;
        int inodeRow = inodeDimension / getAddressDimension();
        long blocchi = sizeFile
                / (int)(getBlockDimension() * Math.pow(2,getBlockExponent()));
       
        blocchi = blocchi - firstInodeDirectBlock;
        countInode++;
        if ((blocchi / inodeRow) > 0) {
            blocchi = blocchi - inodeRow;
            countInode++;
            fullLevel++;
            if ((blocchi / (inodeRow * inodeRow)) > 0) {
                blocchi = blocchi - inodeRow * inodeRow;
                countInode = countInode + 1 + inodeRow;
                fullLevel++;
                if ((blocchi / (inodeRow * inodeRow * inodeRow)) > 0) {
                    countInode = countInode + inodeRow * inodeRow + inodeRow +1;
                    max = true;
                }
            }
        }
        if (max != true && blocchi > 0) {
            if(fullLevel>0) countInode++;
            if(fullLevel==2){
                long plusInode=blocchi/(inodeRow*inodeRow);
                countInode=countInode+plusInode+1;
            }

            double ultimoInode =
                    ((double) blocchi / (double)inodeRow) - blocchi / inodeRow;
            
            if (ultimoInode > 0) {
                countInode = countInode + blocchi / inodeRow + 1;
            } /*else {
                countInode = countInode + blocchi / inodeRow;
            }*/
        }
        usedInodeNumber = usedInodeNumber + countInode;
        return countInode;
    }

    /**
     * Method that calculates the size of maximum file represented by Inode
     * with different address
     * @return an int with the number of blocks you can store with Inode
     */
    int calculateMaxFile() {
        int totalBlocks = firstInodeDirectBlock;
        int inodeRow = inodeDimension / getAddressDimension();
        if (addressLevel > 0) {
            totalBlocks = totalBlocks + inodeRow;
            if (addressLevel > 1) {
                totalBlocks = totalBlocks + inodeRow * inodeRow;
                if (addressLevel == 3) {
                    totalBlocks = totalBlocks + inodeRow * inodeRow * inodeRow;
                }
            }
        }
        return totalBlocks;
    }

    /**
     * Method that calculate the size of max file inserted in hard disk
     */
    void viewMaxFile(){
        super.calculateAddressDimension();
        long maxFile = calculateMaxFile()
                * (int)(getBlockDimension() * Math.pow(2,getBlockExponent()));
        super.setMaxLenghtFile(maxFile);
    }

    /**
     * Method that calculates inflaction index of a file
     * @param sizeFile - the size of file you want to know the inflation index
     * @return an int that returns the inflation index
     */
    double inflationIndex(long sizeFile) {
        return ((double)getInodeNumber(sizeFile) * inodeDimension) / sizeFile;
    }

    /**
     * Method that calculates the inflaction index for every files and makes
     * an average
     * @return average inflaction index
     */
    double inflactionIndexAverage(){
        double average=0;
        for(int index = 0; index < files.size(); index++){
           average = average + inflationIndex(files.elementAt(index).getSize());
        }
        return average/files.size() * 100;
    }

    /**
     * Method that calculates the dimension of File System (Inode)
     * @return an int with dimension of File System
     */
    long fileSystemDimension(){
       long sizeFS = 0; /*Fyle System occupation*/
       for (int it = 0; it < files.size(); it++) {
            sizeFS = sizeFS + getInodeNumber(files.elementAt(it).getSize())
                    * inodeDimension;
       }
       return sizeFS;
    }

    /**
     * Method that calculates the simulation results when it's started:
     * maximum length file
     * Inode number of maximum length file
     * average reading time
     * file system size
     * inflaction index
     * internal fragmentation
     */
    void start() {
        super.calculateAddressDimension();
        int maxFile = calculateMaxFile()
                * (int)(getBlockDimension() * Math.pow(2,getBlockExponent()));
        inodeNumberMaxFile = getInodeNumber(maxFile);
        super.setMaxLenghtFile(maxFile);

        if(super.getChooseAdvanced())
            super.setAverageReading((int) readingTime());

        setSizeFileSystem(fileSystemDimension());
        super.setInternalFragmentation(fragAverage());

        super.setInflationIndex(inflactionIndexAverage());

    }

    /**
     * Open an existing EXT2 simulation
     * @param simulationRoot
     */
    @Override
    void open(Element simulationRoot) {
        Element fileSystemRoot = simulationRoot.getChild("fileSystem");
        inodeDimension = Integer.parseInt((fileSystemRoot.getChild
                ("inodeDimension")).getText());
        firstInodeDirectBlock = Integer.parseInt((fileSystemRoot.
                getChild("firstInodeDirectBlock")).getText());
        addressLevel = Integer.parseInt((fileSystemRoot.
                getChild("addressLevel")).getText());
        super.open(simulationRoot);
    }


    /**
     * Method that set configuration settigs
     * @return vector with configuration settings
     */
    @Override
    public Vector<String[]> getConfigurationValues() {
        Vector<String[]> config = super.getConfigurationValues();

        config.insertElementAt(new String[]{"File System", "EXT2"}, 0);
        config.add(new String[]{Model.translate("addressLevel"),
                    String.valueOf(addressLevel)});
        config.add(new String[]{Model.translate("block1stInode"),
                    String.valueOf(firstInodeDirectBlock)});
        config.add(new String[]{Model.translate("inodeDimension"),
                    String.valueOf(inodeDimension) + " Byte"});

        return config;
    }

    /**
     * Method that customize the result of Ext2 simulation
     * @return a Vector with String results
     */
    public Vector<String[]> getResultsValues(){
        Vector<String[]> results = new Vector<String[]>();

        results.add(new String[]{Model.translate("maxFile"),
                    String.valueOf(getMaxLenghtFile() / 1024) + " KB"});
        results.add(new String[]{Model.translate("internalFragm"),
                    String.valueOf(getInternalFragmentation())});
        results.add(new String[]{Model.translate("inflactionIndex"),
                    String.valueOf(getInflationIndex()) + " %"});
        results.add(new String[]{Model.translate("fileSystemSize"),
                    String.valueOf(super.getSizeFileSystem() / 1024) + " KB"});
        results.add(new String[]{Model.translate("inodeNumber"),
                    String.valueOf(getUsedInodeNumber())});
        if (getChooseAdvanced())
            results.add(new String[]{Model.translate("averageReading"),
                        String.valueOf(getAverageReading()) + " ms"});

        return results;
    }

    /**
     * Method that return inode address level
     * @return address level of inode
     */
    int getAddressLevel() {
        return addressLevel;
    }

    /**
     * Method that set inode address level
     * @param addressLevel int
     */
    void setAddressLevel(int addressLevel) {
        this.addressLevel = addressLevel;
    }

    /**
     * Method that return the number of direct block of first Inode
     * @return direct block of first Inode
     */
    int getFirstInodeDirectBlock() {
        return firstInodeDirectBlock;
    }

    /**
     * Method that set the number of direct block of first Inode
     * @param firstInodeDirectBlock int
     */
    void setFirstInodeDirectBlock(int firstInodeDirectBlock) {
        this.firstInodeDirectBlock = firstInodeDirectBlock;
    }

    /**
     * Method that return Inode dimension
     * @return Inode dimension
     */
    int getInodeDimension() {
        return inodeDimension;
    }

    /**
     * Method that set Inode dimension
     * @param inodeDimension int
     */
    void setInodeDimension(int inodeDimension) {
        this.inodeDimension = inodeDimension;
    }

    /**
     * Method that return the number Inode of max file
     * @return the number Inode of max file
     */
    long getInodeNumberMaxFile() {
        return inodeNumberMaxFile;
    }

    /**
     * Method that set the number Inode of max file
     * @param inodeNumberMaxFile int
     */
    void setInodeNumberMaxFile(int inodeNumberMaxFile) {
        this.inodeNumberMaxFile = inodeNumberMaxFile;
    }

    /**
     *
     * @return
     */
    public long getUsedInodeNumber() {
        return usedInodeNumber;
    }

    /**
     *
     * @param usedInodeNumber
     */
    public void setUsedInodeNumber(long usedInodeNumber) {
        this.usedInodeNumber = usedInodeNumber;
    }
}